﻿using System;
using System.Collections.Generic;
using Algoritmia.Schemes;
using Algoritmia.Utils;
using Lists = Algoritmia.DataStructures.Lists;
using Sets = Algoritmia.DataStructures.Sets;
using Algoritmia.StateSpaces;

namespace Algoritmia.Problems.Puzzles
{
    public class WolfGoatCabbageStateSpace : IReversibleForwardStateSpace<WolfGoatCabbageStateSpace.State, string>
    {

        private readonly static HashSet<string> _wolfGoatSet;

        public class State : IEquatable<State>
        {
            public const string Wolf = "wolf";
            public const string Cabbage = "cabbage";
            public const string Goat = "goat";

            public Sets.ISet<string>[] Shore;
            public int Boat;
            public State Previous { get; private set; }
            public string Decision;

            private readonly Func<Sets.ISet<string>> _createSetOfStrings;

            public State(State s = null,
                Func<Sets.ISet<string>> createSetOfStrings = null)
            {
                _createSetOfStrings = createSetOfStrings ?? (() => AlgoritmiaFactory.Sets<string>.Default);

                Shore = new Sets.ISet<string>[2];
                if (s == null)
                {
                    Boat = 0;
                    Decision = null;
                    Previous = null;
                    var set = _createSetOfStrings();
                    set.Add(Wolf); set.Add(Cabbage); set.Add(Goat);
                    Shore[0] = set;
                    Shore[1] = _createSetOfStrings();
                }
                else
                {
                    Shore[0] = _createSetOfStrings();
                    foreach (var e in s.Shore[0])
                    {
                        Shore[0].Add(e);
                    }
                    Shore[1] = _createSetOfStrings();
                    foreach (var e in s.Shore[1])
                    {
                        Shore[1].Add(e);
                    }
                    Boat = s.Boat;
                    Decision = null;
                    Previous = s;
                }
            }

            public override int GetHashCode()
            {
                int h = Boat;
                foreach(var item in Shore[0])
                {
                    h ^= item.GetHashCode();
                }
                return h;
            }

            public bool Equals(State other)
            {
                return other != null && other.Boat == Boat && other.Shore[0].Equals(Shore[0]);
            }

        }

        static WolfGoatCabbageStateSpace()
        {
            _wolfGoatSet = new HashSet<string> { State.Wolf, State.Goat };
        }


        public State Decide(State state, string decision)
        {
            var newState = new State(state);
            newState.Boat = 1 - state.Boat;
            if (!decision.Equals(""))
            {
                newState.Shore[state.Boat].Remove(decision);
                newState.Shore[newState.Boat].Add(decision);
            }
            newState.Decision = decision;
            return newState;
        }

        public IEnumerable<string> Decisions(State state)
        {
            var currentShore = state.Shore[state.Boat];
            foreach(var d in state.Shore[state.Boat])
            {
                if ( currentShore.Count == 3 && _wolfGoatSet.Contains(d))
                {
                    continue;
                }
                yield return d;
           
            }
            if ( currentShore.Count != 2 || !currentShore.Contains(State.Cabbage))
            {
                yield return "";
            }
        }

        public IEnumerable<State> InitialStates
        {
            get { yield return new State(); }
        }

        public bool IsFinal(State state)
        {
            return state.Boat == 1 && state.Shore[state.Boat].Count == 3;
        }

        public State Undo(State state, string decision)
        {
            return state.Previous;
        }

    }

    public class WolfGoatCabbageSolver
    {
        private readonly BacktrackingEnumerator<WolfGoatCabbageStateSpace.State, string, Lists.IList<string>> _enumerator;

        public WolfGoatCabbageSolver()
        {
            _enumerator = new BacktrackingEnumerator<WolfGoatCabbageStateSpace.State, string, Lists.IList<string>>
                ((space, initial, decisions, final) => decisions
                 , it => AlgoritmiaFactory.Sets<WolfGoatCabbageStateSpace.State>.Default);
        }

        public Lists.IList<string> Solve()
        {
            var space = new WolfGoatCabbageStateSpace();
            return _enumerator.First(space);
        }

    }
}
